package validator

import (
	"fmt"
	"strings"

	"github.com/louloulin/dataflare/pkg/workflow"
)

// ValidationResult represents the result of a workflow validation
type ValidationResult struct {
	// Valid indicates if the workflow is valid
	Valid bool
	// Issues is a list of validation issues
	Issues []string
}

// WorkflowValidator represents a workflow validator
type WorkflowValidator struct {
	// connectorRegistry is the connector registry
	connectorRegistry *workflow.ConnectorRegistry
}

// NewWorkflowValidator creates a new workflow validator
func NewWorkflowValidator() *WorkflowValidator {
	return &WorkflowValidator{
		connectorRegistry: workflow.NewConnectorRegistry(),
	}
}

// ValidateWorkflowDefinition validates a workflow definition
func (v *WorkflowValidator) ValidateWorkflowDefinition(data []byte) (*ValidationResult, error) {
	// Parse workflow definition
	definition, err := workflow.ParseDefinition(data)
	if err != nil {
		return &ValidationResult{
			Valid:  false,
			Issues: []string{fmt.Sprintf("Failed to parse workflow definition: %v", err)},
		}, nil
	}

	// Validate workflow
	issues := v.validateWorkflow(definition)

	// Return validation result
	return &ValidationResult{
		Valid:  len(issues) == 0,
		Issues: issues,
	}, nil
}

// validateWorkflow validates a workflow
func (v *WorkflowValidator) validateWorkflow(definition *workflow.Definition) []string {
	issues := make([]string, 0)

	// Validate basic fields
	if definition.Name == "" {
		issues = append(issues, "Workflow name is required")
	}

	if definition.Version == "" {
		issues = append(issues, "Workflow version is required")
	}

	// Validate sources
	if len(definition.Sources) == 0 {
		issues = append(issues, "At least one source is required")
	}

	for name, source := range definition.Sources {
		if source.Type == "" {
			issues = append(issues, fmt.Sprintf("Source '%s' type is required", name))
		}
	}

	// Validate sinks
	if len(definition.Sinks) == 0 {
		issues = append(issues, "At least one sink is required")
	}

	for name, sink := range definition.Sinks {
		if sink.Type == "" {
			issues = append(issues, fmt.Sprintf("Sink '%s' type is required", name))
		}
	}

	// Validate edges
	if len(definition.Edges) == 0 {
		issues = append(issues, "At least one edge is required")
	}

	// Validate edge connections
	for i, edge := range definition.Edges {
		// Check if source exists
		if _, ok := definition.Sources[edge.From]; !ok {
			if _, ok := definition.Processors[edge.From]; !ok {
				issues = append(issues, fmt.Sprintf("Edge %d: source '%s' does not exist", i+1, edge.From))
			}
		}

		// Check if target exists
		if _, ok := definition.Processors[edge.To]; !ok {
			if _, ok := definition.Sinks[edge.To]; !ok {
				issues = append(issues, fmt.Sprintf("Edge %d: target '%s' does not exist", i+1, edge.To))
			}
		}
	}

	// Validate connectivity
	issues = append(issues, v.validateConnectivity(definition)...)

	// Validate connector types
	issues = append(issues, v.validateConnectorTypes(definition)...)

	return issues
}

// validateConnectivity validates that all components are connected
func (v *WorkflowValidator) validateConnectivity(definition *workflow.Definition) []string {
	issues := make([]string, 0)

	// Check if all sources are connected
	for name := range definition.Sources {
		connected := false
		for _, edge := range definition.Edges {
			if edge.From == name {
				connected = true
				break
			}
		}
		if !connected {
			issues = append(issues, fmt.Sprintf("Source '%s' is not connected to any component", name))
		}
	}

	// Check if all processors are connected
	for name := range definition.Processors {
		// Check if processor has an input
		hasInput := false
		for _, edge := range definition.Edges {
			if edge.To == name {
				hasInput = true
				break
			}
		}
		if !hasInput {
			issues = append(issues, fmt.Sprintf("Processor '%s' has no input", name))
		}

		// Check if processor has an output
		hasOutput := false
		for _, edge := range definition.Edges {
			if edge.From == name {
				hasOutput = true
				break
			}
		}
		if !hasOutput {
			issues = append(issues, fmt.Sprintf("Processor '%s' has no output", name))
		}
	}

	// Check if all sinks are connected
	for name := range definition.Sinks {
		connected := false
		for _, edge := range definition.Edges {
			if edge.To == name {
				connected = true
				break
			}
		}
		if !connected {
			issues = append(issues, fmt.Sprintf("Sink '%s' is not connected to any component", name))
		}
	}

	// Check for cycles
	if v.hasCycle(definition) {
		issues = append(issues, "Workflow contains cycles, which are not allowed")
	}

	return issues
}

// validateConnectorTypes validates connector types
func (v *WorkflowValidator) validateConnectorTypes(definition *workflow.Definition) []string {
	issues := make([]string, 0)

	// Check source connector types
	for name, source := range definition.Sources {
		// Skip validation for unknown connector types
		if strings.HasPrefix(source.Type, "mock") || strings.HasPrefix(source.Type, "test") {
			continue
		}

		// Check if connector type is registered
		factory := v.connectorRegistry.GetFactory()
		_, err := factory.CreateSourceConnector(source.Type)
		if err != nil {
			issues = append(issues, fmt.Sprintf("Source '%s' has unknown connector type '%s'", name, source.Type))
		}
	}

	// Check sink connector types
	for name, sink := range definition.Sinks {
		// Skip validation for unknown connector types
		if strings.HasPrefix(sink.Type, "mock") || strings.HasPrefix(sink.Type, "test") {
			continue
		}

		// Check if connector type is registered
		factory := v.connectorRegistry.GetFactory()
		_, err := factory.CreateSinkConnector(sink.Type)
		if err != nil {
			issues = append(issues, fmt.Sprintf("Sink '%s' has unknown connector type '%s'", name, sink.Type))
		}
	}

	return issues
}

// hasCycle checks if the workflow contains cycles
func (v *WorkflowValidator) hasCycle(definition *workflow.Definition) bool {
	// Build adjacency list
	adjacencyList := make(map[string][]string)
	for _, edge := range definition.Edges {
		adjacencyList[edge.From] = append(adjacencyList[edge.From], edge.To)
	}

	// Check for cycles using DFS
	visited := make(map[string]bool)
	recStack := make(map[string]bool)

	// Start DFS from each source
	for name := range definition.Sources {
		if v.dfsHasCycle(name, adjacencyList, visited, recStack) {
			return true
		}
	}

	return false
}

// dfsHasCycle performs DFS to check for cycles
func (v *WorkflowValidator) dfsHasCycle(node string, adjacencyList map[string][]string, visited, recStack map[string]bool) bool {
	// If node is already in recursion stack, we found a cycle
	if recStack[node] {
		return true
	}

	// If node is already visited, no need to process again
	if visited[node] {
		return false
	}

	// Mark node as visited and add to recursion stack
	visited[node] = true
	recStack[node] = true

	// Visit all neighbors
	for _, neighbor := range adjacencyList[node] {
		if v.dfsHasCycle(neighbor, adjacencyList, visited, recStack) {
			return true
		}
	}

	// Remove node from recursion stack
	recStack[node] = false

	return false
}
